﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;

namespace GoodTrader.GTOption
{
    public class OpPriceManager
    {
        // 행사가에 따른 옵션 가격 목록
        private Dictionary<string, GTOption.PriceData> mOpPriceDataList = new Dictionary<string,GTOption.PriceData>();
        public Dictionary<string, GTOption.PriceData> OpPriceDataList
        {
            get { return mOpPriceDataList; }
            set { mOpPriceDataList = value; }
        }
        // 옵션 일별 데이터를 담고 있는 컨테이너
        private List<GTDownload.StockData> mOpDayData = new List<GTDownload.StockData>();
        private const int MaxOpenDays = 50;
        // 선물 코드 관리자. 각종 선물의 코드를 가져 올 수 있다.
        protected CPUTILLib.CpFutureCode mFtCodeMgr = new CPUTILLib.CpFutureCode();
        // 옵션 코드 관리자. 각종 옵션 코드를 가져올 수 있다.
        private GTDownload.CpOptionCodeManager mOpCodeMgr = null;
        public GTDownload.CpOptionCodeManager OpCodeMgr
        {
            get { return mOpCodeMgr; }
            set { mOpCodeMgr = value; }
        }
        // 옵션과 선물의 차트 데이터를 가져오는 객체.
        private CPSYSDIBLib.FutOptChartClass mCpChartFetcher = new CPSYSDIBLib.FutOptChartClass();
        // 영업일 목록
        private double[] arrOpenDays= new double[MaxOpenDays];
        // 만기일 목록
        private List<string> mExpDayList = new List<string>();

        private double mOpExpDay = 0.0;
        private double mOpStartDay = 0.0;
        public bool mPrepared = false;
        // 저마기목월 가격이 계산되면 참이된다.
        public bool mFoundPrice = false;
        public bool FoundPrice
        {
            get { return mFoundPrice; }
            set { mFoundPrice = value; }
        }
       
        /**
         * @brief 이 함수는 초기화를 수행한다. 초기화에는 옵션 만기일 목록읽어오기, 옵션 영업일 받아오기가 포함이 된다.
         * @remark 초기화에는 편리함을 증가시키기 위하여 가장 최근 만기일과 옵션 영업 시작일을 얻어오는 과정이 포함되어 있다.
         *         
         */ 
        public void Prepare()
        {
            // 만기일 읽어 오기 - 파일에서 읽어 옴
            ReadOpExpDate();
            // 영업일 읽어 오기 - 증권사에서 읽어 옴.
            ReadOpenDays();
            // 만기일 찾기
            FindRecentExpDay(GTGlobal.mEndDate, ref mOpExpDay);
            // 옵션 시작일 찾기
            FindRecentStartDay(mOpExpDay, ref mOpStartDay);
            mPrepared = true;
        }

        /**
         * @brief : 이 함수는 옵션 코드 관리자에서 코드를 가져와 코드 개수만큼 데이터 컨테이너를 만들어 준다.
         */
        private void MakeOpDayList()
        {
            if (mOpCodeMgr == null && mPrepared == true)
                return;

            // 영업일을 먼저 계산한다.
            int openedDays = CalcOpenDays(GTGlobal.mEndDate, mOpStartDay);
            // 옵션 코드 관리자에서 옵션 가격 목록을 가져온다.
            List<string> priceList = mOpCodeMgr.RMPriceList;
            for(int i = 0; i < priceList.Count; i++)
            {
                GTDownload.StockData sData = new GTDownload.StockData(openedDays);
                sData.Code = priceList[i];
                mOpDayData.Add(sData);
            }
        }

        /**
         * @brief 이 함수는 옵션 일별 가격 목록을 이용하여 옵션 일별 데이터를 내려받는다. 
         */ 
        public void DownloadOpDayList()
        {
            // 옵션 가격 목록을 가지고 옵션 일별 데이터 목록을 만든다.
            MakeOpDayList();
            if (mOpDayData.Count <= 0)
                return;
            for (int i = 0; i < mOpDayData.Count; i++)
            {
                GTDownload.StockData data = mOpDayData[i];
                DownloadOpDayData(data);
            }
        }

        /**
         * @brief 이 함수는 가격 코드와 데이터 배열을 받아 옵션 일별 데이터를 가져온다.
         * @param aData : 데이터를 담을 컨테이너
         * @remark 일별 데이터는 만기일 다음날 부터 현재까지이다.
         */
        private void DownloadOpDayData(GTDownload.StockData aData)
        {
            // 날짜, 시간, 시가, 고가, 저가, 종가 만을 가져온다.
            int[] fields = new int[] { 0, 1, 2, 3, 4, 5 };
            mCpChartFetcher.SetInputValue(0, aData.Code);
            mCpChartFetcher.SetInputValue(1, '1'); // 기간으로 가져옴
            mCpChartFetcher.SetInputValue(2, GTGlobal.mEndDate); // 요청 종료일
            mCpChartFetcher.SetInputValue(3, mOpStartDay); // 요청 시작일
            mCpChartFetcher.SetInputValue(4, 100); // 기간으로 가져오기 때문에 상관없음 
            mCpChartFetcher.SetInputValue(5, fields);
            mCpChartFetcher.SetInputValue(6, 'D'); // 일별 데이터 가져옴
            mCpChartFetcher.SetInputValue(7, 1); // 주기는 1임

            try
            {
                // 데이터 수신 요청을 한다.
                mCpChartFetcher.BlockRequest();
            }
            catch (System.Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }

            object dataCount = mCpChartFetcher.GetHeaderValue(3);

            for (int i = Convert.ToInt32(dataCount) - 1, j = 0; i > -1; i--, j++)
            {
                aData.Date[j] = Convert.ToDouble(mCpChartFetcher.GetDataValue(0, i).ToString()); // 날짜
                aData.Time[j] = Convert.ToDouble(mCpChartFetcher.GetDataValue(1, i).ToString()); // 시간
                aData.Open[j] = Convert.ToDouble(mCpChartFetcher.GetDataValue(2, i).ToString()); // 시가
                aData.High[j] = Convert.ToDouble(mCpChartFetcher.GetDataValue(3, i).ToString()); // 고가
                aData.Low[j] = Convert.ToDouble(mCpChartFetcher.GetDataValue(4, i).ToString()); // 저가
                aData.Close[j] = Convert.ToDouble(mCpChartFetcher.GetDataValue(5, i).ToString()); // 종가
            }
        }

        /**
         * @brief 이 함수는 현재날짜에서 부터 시작일까지 영업일을 계산한다.
         * @param aCurDay : 현재 날짜
         * @param aStartDay : 만기 다음날의 시작일
         * @remark 영업일은 선물의 일별 데이터를 기준으로 한다.
         */ 
        private int CalcOpenDays(double aCurDay, double aStartDay)
        {
            int count = 0;
            int startIndex = 0;
            int endIndex = 0;
            for(int i = arrOpenDays.Length - 1; i >= 0; i--)
            {
                double openDay = arrOpenDays[i];
                if (aCurDay == openDay)
                    endIndex = i;
                if (aStartDay == openDay)
                    startIndex = i;
            }

            count = Math.Abs(endIndex - startIndex);
            // 시작인데스에서 종료인덱스를 뺀 값에 1을 더하면 개수가 나온다.
            return count + 1;
        }

        /**
         * @brief 이 함수는 옵션 일별 데이터 목록을 가지고 월고, 월저, 기준가, 목표가, 마디가를 찾는다. 
         */ 
        public void FindPrice()
        {
            for(int i = 0; i <mOpDayData.Count; i++)
            {
                GTDownload.StockData sData = mOpDayData[i];
                GTOption.PriceData pData = new GTOption.PriceData();
                FindMonthHigh(sData, ref pData);
                FindMonthLow(sData, ref pData);
                FindMonthBase(sData, ref pData);
                FindMadi(sData, ref pData);
                FindMogpyo(sData, ref pData);
                mOpPriceDataList.Add(sData.Code, pData); 
            }

            mFoundPrice = true;
        }

        /**
         * @brief 이 함수는 월고를 찾아 낸다. 
         * @param aSData : 영업일 동안의 일별 데이터 원본
         * @param aPData : 결과를 넣을 데이터
         * @remark 월고는 0의 값을 가질 수가 없다.
         */ 
        private void FindMonthHigh(GTDownload.StockData aSData, ref GTOption.PriceData aPData)
        {
            // 가장 작은 값을 찾는다.
            double mhigh = aSData.High.Min();
            for (int i = 0; i < aSData.High.Length; i++)
            {
                double high = aSData.High[i];
                if (high != 0.0 && high > mhigh)
                    mhigh = high;
            }
            aPData.MonthHigh = mhigh;
        }

        /**
         * @brief 이 함수는 월저를 찾아 낸다. 
         * @param aSData : 영업일 동안의 일별 데이터 원본
         * @param aPData : 결과를 넣을 데이터
         * @remark 월저는 0.0이 될 수 없다.
         */
        private void FindMonthLow(GTDownload.StockData aSData, ref GTOption.PriceData aPData)
        {
            double mlow = aSData.Low.Max();
            for (int i = 0; i < aSData.Low.Length; i++)
            {
                double low = aSData.Low[i];
                if (low != 0.0 && low < mlow)
                    mlow = low;
            }
            aPData.MonthLow = mlow;
        }

        /**
         * @brief 이 함수는 기준가를 찾아 낸다. 
         * @param aSData : 영업일 동안의 일별 데이터 원본
         * @param aPData : 결과를 넣을 데이터
         * @remark 월기준가는 0이 아니고 시작일의 저가가 월기준가이고 시작일이 없을 때에는
         *         시작일 이후의 첫번째 영업일의 저가가 월저가가 된다.
         *         시작일에 기준가가 없는 경우는 시작일 이후에 가격이 새로 탄생했기 때문이다.
         */
        private void FindMonthBase(GTDownload.StockData aSData, ref GTOption.PriceData aPData)
        {
            double mBase = 0.0;
            // 처음데이터가 나타나는 첫번째 데이터가 기준가가 된다.
            for(int i = 0; i < aSData.Low.Length; i++)
            {
                double low = aSData.Low[i];
                if (low != 0.0)
                {
                    mBase = low;
                    break;
                }
            }

            aPData.MonthBase = mBase;
        }

        /**
         * @brief 이 함수는 기준가를 찾아 낸다. 
         * @param aSData : 영업일 동안의 일별 데이터 원본
         * @param aPData : 결과를 넣을 데이터
         * @remark 월기준가는 0이 아니고 시작일의 저가가 월기준가이고 시작일이 없을 때에는
         *         시작일 이후의 첫번째 영업일의 저가가 월저가가 된다.
         *         시작일에 기준가가 없는 경우는 시작일 이후에 가격이 새로 탄생했기 때문이다.
         */
        private void FindMadi(GTDownload.StockData aSData, ref GTOption.PriceData aPData)
        {
            double mBase = 0.0;
            // 처음데이터가 나타나는 첫번째 데이터가 기준가가 된다.
            for (int i = 0; i < aSData.Low.Length; i++)
            {
                double low = aSData.Low[i];
                if (low != 0.0)
                {
                    mBase = low;
                    break;
                }
            }

            aPData.Madi = mBase;
        }

        /**
         * @brief 이 함수는 기준가를 찾아 낸다. 
         * @param aSData : 영업일 동안의 일별 데이터 원본
         * @param aPData : 결과를 넣을 데이터
         * @remark 월기준가는 0이 아니고 시작일의 저가가 월기준가이고 시작일이 없을 때에는
         *         시작일 이후의 첫번째 영업일의 저가가 월저가가 된다.
         *         시작일에 기준가가 없는 경우는 시작일 이후에 가격이 새로 탄생했기 때문이다.
         */
        private void FindMogpyo(GTDownload.StockData aSData, ref GTOption.PriceData aPData)
        {
            double mBase = 0.0;
            // 처음데이터가 나타나는 첫번째 데이터가 기준가가 된다.
            for (int i = 0; i < aSData.Low.Length; i++)
            {
                double low = aSData.Low[i];
                if (low != 0.0)
                {
                    mBase = low;
                    break;
                }
            }

            aPData.Mogpyo = mBase;
        }

        /**
         * @brief 만기일 목록을 읽어온다. 만기일은 파일에 정의되어 있다.
         */ 
        private void ReadOpExpDate()
        {
            string fileName = Directory.GetCurrentDirectory();
            fileName += "\\OpExpDate.txt";
            if (File.Exists(fileName))
            {
                using (StreamReader sr = new StreamReader(fileName))
                {
                    int i = 0;
                    while (sr.Peek() >= 0)
                    {
                        string opExDate = sr.ReadLine();
                        //arrOptExpDate[i] = opExDate;
                        mExpDayList.Add(opExDate);
                        i++;
                    }
                }
            }
        }

        /**
         * @brief 영업일 목록을 읽어 온다.
         * @remark 영업일은 선물 일 데이터를 기준으로 한다. 선물 일데이터가 존재하면 영업을 한 날로 간주한다.
         */ 
        private void ReadOpenDays()
        {
            double curDay = GTGlobal.mEndDate;
            object code = mFtCodeMgr.GetData(0, 0);
            // 날짜, 시간, 시가, 고가, 저가, 종가 만을 가져온다.
            int[] fields = new int[] { 0, 1, 2, 3, 4, 5 };
            mCpChartFetcher.SetInputValue(0, code);
            mCpChartFetcher.SetInputValue(1, '2'); // 개수로 가져옴
            mCpChartFetcher.SetInputValue(2, curDay); // 현재 날짜 기준
            mCpChartFetcher.SetInputValue(3, curDay);
            mCpChartFetcher.SetInputValue(4, MaxOpenDays); // 정해진 개수를 가져옴 
            mCpChartFetcher.SetInputValue(5, fields);
            mCpChartFetcher.SetInputValue(6, 'D'); // 일별 데이터 가져옴
            mCpChartFetcher.SetInputValue(7, 1); // 주기는 1임

            try
            {
                // 데이터 수신 요청을 한다.
                mCpChartFetcher.BlockRequest();
            }
            catch (System.Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }

            object dataCount = mCpChartFetcher.GetHeaderValue(3);

            for (int i = Convert.ToInt32(dataCount) - 1, j = 0; i > -1; i--, j++)
            {
                // 날짜 만을 받아 온다.
                arrOpenDays[j] = Convert.ToDouble(mCpChartFetcher.GetDataValue(0, i)); // 날짜
            }
        }

        /**
        /* @brief 영업일 목록에서 현재날짜에서 가장 가까운 과거 만기일과 그 다음날을 찾아온다.
         * @param aCurDay : 현재 날짜
         * @param aExpDay : 찾고자 하는 만기일
         */
        private bool FindRecentExpDay(double aCurDay, ref double aExpDay)
        {
            aExpDay = 0.0;
            bool found = false;
            // 만기일은 날짜가 작은 것부터 오름차순으로 정렬되어 있다.
            // 따라서 현재 날짜보다 작은 첫번째 만기일이 가장 최근의 만기일이다.
            for (int i = mExpDayList.Count - 1; i < mExpDayList.Count; i--)
            {
                double expDay = Convert.ToDouble(mExpDayList[i]);
                if (expDay < aCurDay)
                {
                    aExpDay = expDay;
                    found = true;
                    break;
                }
            }

            return found;
        }

        /**
         * brief 만기일을 가지고 시작일을 찾는다.
         */
        private bool FindRecentStartDay(double aExpDay, ref double aStartDay)
        {
            bool found = false;
            // 시작일은 영업일 중에서 만기일 다음날이다. 
            // 영업일은 오름차순으로 정렬되어 있다.
            for (int i = 0; i < arrOpenDays.Length; i++)
            {
                double openDay = arrOpenDays[i];
                if (openDay == aExpDay)
                {
                    if (i + 1 < arrOpenDays.Length)
                    {
                        aStartDay = arrOpenDays[i + 1];
                        found = true;
                        break;
                    }
                }
            }

            return found;
        }
    }
}
